/*
 * Player.java
 *
 * Created on 28 de Agosto de 2010, 12:50
 *
 */

package intopo.evogame.partnerselection.player;

import intopo.evogame.partnerselection.Cache;
import intopo.evogame.partnerselection.Database;
import intopo.evogame.partnerselection.chromosome.Chromosome;
import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import intopo.evogame.partnerselection.config.ParsingException;
import intopo.evogame.partnerselection.population.Node;
import intopo.game.Strategy;
import intopo.game.Game;
import intopo.util.Util;

import util.adt.Collection;
import util.adt.O1Collection;

import java.io.IOException;
import java.io.PrintStream;
import java.util.Scanner;

/**
 * Represents basic data of a player used by the evolutionary algorithm to
 * analyse a partner selection model.
 *
 * <p> Files written by instances of this class:
 *
 * <ul>
 * <li>probability-vector-ID.txt</li>
 * <li>combination-vector-ID.txt</li>
 * <li>neighbours-ID.txt</li>
 * <li>strategy-statistics-ID.txt</li>
 * </ul>
 *
 * @param S The class that represents the strategy used to play the game.
 * @param C The specific chromosome that contains the genotype of this
 * player.
 * @param P The specific player class that implements the abstract methods
 * of this class.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Lopes da Silva Mariano</a>
 *
 * @version 1.0 28 de Agosto de 2010
 */
public abstract class AbstractPlayer<
	S extends Strategy<S>,
	C extends Chromosome<S>,
	P extends AbstractPlayer<S, C, P>>
{
	/**
	 * Constant used to mark a player has having no parent.
	 */
	static final public int NO_PARENT = -1;
	/**
	 * Player identification.
	 */
	final int ID;
	/**
	 * Identification of the parent of this player.
	 * 
	 * <p> We only store the parent id instead of a reference to the class.
	 * If we stored the reference, even if a player died of overcrowding or other cause, a reference to its object would linger through.
	 * Therefore, memory footprint of the application would steadly increase wit the number of constructed players.
	 */
	final int parentID;
	/**
	 * The chromosome of this player.  The basic chromosome specifies the
	 * size of the probability and combination vectors.  It also specifies
	 * the strategy used by the player.
	 */
	final protected C chromosome;
	/**
	 * Current energy of this player.  Energy is obtained by playing games.
	 *
	 * <p> This field is part of player's phenotype.
	 */
	protected float energy;
	/**
	 * Probability and combination
	 * vectors.  The first contains the probability to select a combination
	 * of this player's neighbours.  The second contains combinations of
	 * this player's neighbours.
	 *
	 * <p> This field is part of player's phenotype.
	 */
	protected ProbabilityCombinationVectors<S, C, P> probabilityCombinationVectors;
	/**
	 * Set of candidate partners of this player.
	 */
	final protected O1Collection<P> neighbours;
	/**
	 * Node where the player was born.
	 */
	final protected Node<S, C, P> node;
	/**
	 * This field is used to generate unique filenames for each instance of
	 * this class.
	 */
	static private int idGenerator = 0;

	/**
	 * Partner combination used when the player's probability and
	 * combination vectors have size 0.
	 */
	transient ProbabilityCombinationVectors.PartnerCombination<S, C, P> workComb;

	/**
	 * Creates a new instance of Player.
	 *
	 * <p> Field {@code probabilityCombinationVectors} is not
	 * initialised.  The probability and combination vectors are only
	 * initalised after all players in a population have been constructed and
	 * their set of candidate partners has been filled.
	 * @see #initProbabilityCombinationVectors()
	 */
	protected AbstractPlayer (C chromosome, int parentID, Node<S, C, P> node, int bornTime)
		throws IOException
	{
		this.ID = idGenerator++;
		this.parentID = parentID;
		this.chromosome = chromosome;
		this.energy = 0.0f;
		this.neighbours = new O1Collection<P> (Parameters.setCandidatePartnersCapacity);
		this.node = node;
		this.writePlayer (bornTime);
	}

	/**
	 * Initialise the probability, combination and weight vectors and the
	 * set of potential partners.
	 *
	 * <p> This method is called after all players have been constructed and
	 * their set of candidate partners has been filled.
	 * 
	 * @see #AbstractPlayer(intopo.evogame.partnerselection.chromosome.Chromosome, int)
	 */
	final public void initProbabilityCombinationVectors ()
	{
		if (this.neighbours.size () >= Cache.numberPartners) {
			if (this.chromosome.poolSize == 0) {
				this.workComb = new ProbabilityCombinationVectors.PartnerCombination<S, C, P> (this.neighbours);
			}
			else { // && this.chromosome.poolSize > 0
				this.probabilityCombinationVectors = new ProbabilityCombinationVectors<S, C, P> (chromosome, this.neighbours);
			}
		}
		else {
			System.err.println ("initProbabilityCombinationVectors: neighbours.size "
					  + this.neighbours.size ()
					  + "   Cache.numberPartners " + Cache.numberPartners);
		}
	}
	
	/**
	 * Calls {@code probabilityCombinationVectors.checkInvalidCombinations()} method.
	 * This method is called after all dead players in a population have been removed from the set of candidate partners and new born players have been inserted.
	 */
	final public void checkInvalidCombinations ()
	{
		if (chromosome.poolSize > 0) {
			if (this.probabilityCombinationVectors == null) {
				if (this.neighbours.size () >= Cache.numberPartners) {
					this.probabilityCombinationVectors = new ProbabilityCombinationVectors<S, C, P> (chromosome, this.neighbours);
				}
			}
			else {
				this.probabilityCombinationVectors.checkInvalidCombinations();
			}
		}
	}

	/**
	 * Return the strategy used by this player.
	 */
	final public S getStrategy ()
	{
		return chromosome.strategy;
	}
	/**
	 * Get the chromosome of this player.
	 * @return the chromosome of this player.
	 */
	final public C getChromosome ()
	{
		return this.chromosome;
	}
	/**
	 * Get the unique identification of this player.  This id is used in the database to represent the primary key of the <i>player</i> table.
	 * It is also used as the foreign key in other tables.
	 *
	 * <p> Id are created using static field {@code idGenerator}.
	 *
	 * @see #idGenerator
	 * @see intopo.evogame.partnerselection.Database
	 * @return the unique identification of this player.
	 */
	final public int getID ()
	{
		return this.ID;
	}
	/**
	 * Return the set of neighbours of this player.
	 */
	final protected Collection<P> getNeighbours ()
	{
		return this.neighbours;
	}

	/**
	 * Removes the given player from the set of candidate partners.  The
	 * combination vector is not updated.  Only when all dead players have
	 * been removed from the set of candidate partners and born players have
	 * been added to this set, we update the combination vector. This is
	 * done in method {@code checkInvalidCombinations()}.
	 * @see #checkInvalidCombinations()
	 */
	final public void neighbourDied (P n)
	{
		this.neighbours.remove (n);
	}

	/**
	 * Adds the given player to the set of potential partners and updates
	 * relevant data.
	 */
	final public void addNeighbour (P n)
	{
		this.neighbours.add (n);
	}


	/**
	 * <p> Updates the player's energy and other statistical data.  This
	 * method is called by the player that was selected by a neighbour.

	 * <p> The player's energy is increased by the payoff he obtained.  In
	 * order to avoid negative payoffs, we subtract the minimum payoff
	 * obtained in the game.
	 * 
	 * <p> The minimum payoff obtained in a game is stored in class {@code
	 * Cache}.  This class contains global constant values that are
	 * initialised in runtime after reading a configuration file.
	 *
	 * @param payoff The payoff obtained by this player.
	 */
	final protected void postGame (float payoff)
	{
		energy += payoff - Cache.minPayoff;
	}

	/**
	 * Update the probability and combination vectors, energy and other
	 * statistical data. This method is called by the player that selected
	 * partners.
	 *
	 * <p> If the probability vector is updated, the time complexity of this
	 * method is {@code O(n)} where {@code n} is the number of
	 * combinations. The probability vector is stored as partial sums.  In
	 * the first games, this vector is updated more frequently. The time
	 * lost in the update is gained in partner selection as it uses binary
	 * search.
	 *
	 * @param payoff The payoff obtained by this player.
	 * @param combinationIndex The index of partner combination.
	 */
	final protected void postGame (float payoff, int combinationIndex)
	{
		energy += payoff - Cache.minPayoff;

		if (combinationIndex != -1
			&& payoff < chromosome.utilityThreshold
			&& this.chromosome.poolSize > 0) {
			// distribute probability
			if (this.chromosome.poolSize > 1) {
				int oldProbability = this.probabilityCombinationVectors.getRawProbability (combinationIndex);
				int newProbability = (int) Math.round (oldProbability * (1 - chromosome.delta));
				int decrease = oldProbability - newProbability;
				int remainderIndex = Util.nextPositiveInt (this.chromosome.poolSize);
				this.probabilityCombinationVectors.distributeProbability(combinationIndex, decrease, remainderIndex);
			}
			// select a random combination different from those in the combination vector
			this.probabilityCombinationVectors.replaceCombination (combinationIndex);
		}
	}
	/**
	 * If this player is capable of reproducing, return a new instance
	 * representing the newborn.  Otherwise {@code null} should be returned.
	 *
	 * <p> In order to reproduce, players must accumulate energy.  The
	 * reproduction threshold is given by parameter {@code }.  Players
	 * obtain energy by playing games.

	 * @return An instance of the offspring or {@code null} if the player
	 * cannot reproduce.
	 * @throws IOException if an IO error occurred while writing the data of
	 * the offspring in the player table.
	 */
	abstract public C reproduction ()
		throws IOException;

	/**
	 * Returns {@code true} if this player dies.  Players may die from
	 * overcrowding, from old age or some combination of both.
	 *
	 * <p> Implementors of this method should only consider internal causes
	 * of death.  Overcrowding is handled by classes in package {@code
	 * intopo.evogame.partnerselection.population}.
	 */
	abstract public boolean dies ();

	/**
	 * Play a game between this player and a set of partners.
	 *
	 * <p> Parameters {@code strategies} and {@code players} are volatile
	 * fields in class {@code EvolutionPartnerSelection}.
	 *
	 * <p> Information about the partners selected is written to table
	 * {@code Database#gamesSelected}.  This is done in method {@code
	 * writeGamesSelected(int,ProbabilityCombinationVectors.PartnerCombination)}.
	 *
	 * <p> This player and selected partners have their energy increased.
	 *
	 * @param strategies Auxiliary parameter to store the strategies used to
	 * compute the game outcome
	 * @param players Auxiliary parameter to store the players that are
	 * going to play the game.
	 */
	public void playGame (int time, Game<S> game, Game.Strategies<S> strategies, AbstractPlayer[] players)
		throws IOException
	{
		int i;
		// check if I can play a game
		if (this.neighbours.size () < Cache.numberPartners) {
			System.out.format ("%d cannot play\n", this.ID);
			return ;
		}
		// compute players that are going to play
		players [0] = this;
		strategies.putStrategy (0, chromosome.strategy);
		// select partners
		int selected;
		ProbabilityCombinationVectors.PartnerCombination<S, C, P> pc;
		if (this.chromosome.poolSize == 0) {
			selected = -1;
			try {
				this.workComb.combination.newSubset (Cache.supply);
			}
			catch (java.lang.RuntimeException e) {
				System.out.println ("ERROR: Candidate partners is small");
				debug ();
				throw e;
			}
			pc = this.workComb;
		}
		else {
			selected = this.probabilityCombinationVectors.randomCombination ();
			pc = this.probabilityCombinationVectors.getCombination (selected);
		}
		// prepare strategies and players to play the game
		try {
			for (i = Cache.numberPlayers - 1; i > 0; i--) {
				P p = pc.combination.get (i - 1);
				players [i] = p;
				strategies.putStrategy (i, p.chromosome.strategy);
			}
		}
		catch (RuntimeException e) {
			Cache.debug ();
			System.out.println ();
			System.out.println (strategies);
			System.out.println ();
			debug ();
			System.out.println ();
			throw e;
		}
		this.writeGamesSelect (time, pc);
		// play the game
		try {
			game.play (strategies);
		}
		catch (RuntimeException e) {
			Cache.debug ();
			System.out.println ();
			System.out.println (strategies);
			System.out.println ();
			debug ();
			System.out.println ();
			throw e;
		}
		// update players' state
		postGame (strategies.getPayoff (0), selected);
		for (i = Cache.numberPlayers - 1; i > 0; i--) {
			// update partner's state
			players [i].postGame (strategies.getPayoff (i));
		}
	}
	/**
	 * Write a record in table {@code player}.
	 * Players must be written in ascending order of their identification.
	 * Players are created by {@code Node} instances.  In geographic populations, nodes are processed in the same order.  This guarantees that player creating and writes to the table are performed in the same order.
	 * 
	 *
	 * @param bornTime The time when this player was born.
	 * @param node  The identification of the place where he was born.
	 * @throws java.io.IOException
	 * @see intopo.evogame.partnerselection.Database#player  For the record format.
	 */
	final private void writePlayer (int bornTime)
		throws IOException
	{
		Database.singleton.player.writeInt (this.ID);
		Database.singleton.player.writeInt (this.chromosome.ID);
		Database.singleton.player.writeInt (this.node.ID);
		Database.singleton.player.writeInt (parentID);
		Database.singleton.player.writeInt (bornTime);
		Database.singleton.player.writeInt (-1); // death time
	}
	final public void writePlayerDeath (int deathTime)
		throws IOException
	{
		Database.singleton.playerDeath.writeInt (this.ID);
		Database.singleton.playerDeath.writeInt (deathTime);
	}

	final public void writePhenotype (int time)
		throws IOException
	{
		Database.singleton.playerPhenotype.writeInt (this.ID);
		Database.singleton.playerPhenotype.writeInt (time);
		Database.singleton.playerPhenotype.writeFloat (this.energy);
	}
	final public void writeGamesSelect (int time, ProbabilityCombinationVectors.PartnerCombination<S, C, P> pc)
		throws IOException
	{
		for (P p : pc.combination) {
			Database.singleton.gamesSelected.writeInt (time);
			Database.singleton.gamesSelected.writeInt (this.ID);
			Database.singleton.gamesSelected.writeInt (p.ID);
		}
	}
	final public void writeProbabilityVectorCombination (int time)
		throws IOException
	{
		int last = 0;
		int i;
		int size = this.probabilityCombinationVectors.size ();
		for (i = 0; i < size; i++) {
			ProbabilityCombinationVectors.PartnerCombination<S, C, P> pc = this.probabilityCombinationVectors.getSlot (i);
			int pps = pc.getProbabilityPartialSum();
			Database.singleton.probabilityVectorCombination.writeInt (this.ID);
			Database.singleton.probabilityVectorCombination.writeInt (time);
			Database.singleton.probabilityVectorCombination.writeByte (i);
			Database.singleton.probabilityVectorCombination.writeInt (pps - last);
			last = pps;
		}
	}
	final public void writeProbabilityVectorCombinationPartner (int round)
		throws IOException
	{
		int size = this.probabilityCombinationVectors.size ();
		for (int i = size - 1; i >= 0; i--) {
			ProbabilityCombinationVectors.PartnerCombination<S, C, P> pc = this.probabilityCombinationVectors.getSlot (i);
			for (int j = Cache.numberPartners - 1; j >= 0; j--) {
				Database.singleton.probabilityVectorCombinationPartner.writeInt (this.ID);
				Database.singleton.probabilityVectorCombinationPartner.writeInt (round);
				Database.singleton.probabilityVectorCombinationPartner.writeByte (j);
				Database.singleton.probabilityVectorCombinationPartner.writeInt (pc.combination.get (j).ID);
			}
		}
	}

	@Override
	public int hashCode ()
	{
		return this.ID;
	}

	@Override
	public boolean equals (Object o)
	{
		try {
			AbstractPlayer<?, ?, ?> ap = (AbstractPlayer<?, ?, ?>) o;
			return this == ap;
		}
		catch (java.lang.ClassCastException cce) {
			return false;
		}
	}

	@Override
	public String toString ()
	{
		return Integer.toString (this.ID);
//		return  "\t" + this.probabilityCombinationVectors;
	}

	public void debug ()
	{
		System.out.format ("ID %d  energy %f  neighbour", this.ID, this.energy);
		for (P p : this.neighbours) {
			System.out.format (" %d", p.ID);
		}
		System.out.println ();
		System.out.format ("chromosome: %s\n", this.chromosome);
		System.out.println (this.probabilityCombinationVectors);
		System.out.format ("wc: %s\n", this.workComb);
	}
	public void debug1 ()
	{
		System.out.println ("energy: " + this.energy);
		System.out.println ("chromosome: " + this.chromosome.toString ());
		System.out.print ("probability vector:");
		int last = 0;
		for (ProbabilityCombinationVectors.PartnerCombination pc : this.probabilityCombinationVectors) {
			System.out.print (" " + (pc.getProbabilityPartialSum() - last));
			last = pc.getProbabilityPartialSum();
		}
		System.out.print ("\nCombination vector:");
		for (ProbabilityCombinationVectors.PartnerCombination<S, C, P> pc : this.probabilityCombinationVectors) {
			System.out.print (" ");
			for (int i = 0; i < pc.combination.size (); i++) {
				P p = pc.combination.get (i);
				if (i != 0) {
					System.out.print ("_");
				}
				System.out.print (p.ID);
			}
			System.out.print (" " + (pc.getProbabilityPartialSum() - last));
			last = pc.getProbabilityPartialSum();
		}
		System.out.println ();
	}
	/**
	 * Parameters used by all instances of class {@code AbstractPlayer}.
	 * These parameters control the following behaviour:
	 *
	 * <ul> <li>Reproduction phase: players need to accumulate a certain
	 * amount of energy in order to be able to reproduce.  Energy is
	 * obtained by playing games.
	 *
	 * <li> Distinct combinations in the combination vector: if cannot be
	 * repeated combinations, the partner selection algorithm takes longer
	 * to converge.  Worse, if there are not enough good combinations, it
	 * will never converge.  However distinct combinations may reduce the
	 * impact of good partners dying.
	 *
	 * <li> The default capacity of the set of candidate partners.  This is
	 * an internal parameter used in constructing instances of class {@code
	 * O1Collection}.  This is an ADT whose operations have O(1) time
	 * complexity in order to fasten execution time. </ul>
	 */
	static public class Parameters
	{
		/**
		 * Energy necessary for a player to reproduce.  Players start with
		 * zero energy and gain energy by playing games.  Upon reproduction
		 * player's energy goes back to zero.
		 */
		static public float energyReproduction;
		/**
		 * Indicates if the combination vector should contain distinct
		 * combinations or not.
		 */
		static public boolean distinctCombinations;
		/**
		 * Default capacity of the set of candidate partners.
		 */
		static public int setCandidatePartnersCapacity;
		/**
		 * Reads from the given scanner the parameters that are used by all
		 * instances of classes extended from {@code AbstractPlayer}.
		 *
		 * <p> If the scanner does not have the correct data, the behaviour
		 * of the program is given by the implementation of method {@code
		 * Util.nextInt(Scanner,String)}.
		 */
		static public void read (Scanner scanner)
			throws ParsingException
		{
			AbstractPlayer.Parameters.distinctCombinations = AbstractTextConfigFile.readInt (scanner, "distinct combinations") == 1;
			AbstractPlayer.Parameters.energyReproduction = AbstractTextConfigFile.readFloat (scanner, "energy reproduction");
			AbstractPlayer.Parameters.setCandidatePartnersCapacity = AbstractTextConfigFile.readInt (scanner, "capacity of the set of candidate partners");
		}

		public static void write (PrintStream ps)
		{
			ps.print (AbstractPlayer.Parameters.distinctCombinations ? 1 : 0);
			ps.println ("\t# distinct combinations");
			ps.print (AbstractPlayer.Parameters.energyReproduction);
			ps.println ("\t# energy reproduction");
			ps.print (AbstractPlayer.Parameters.setCandidatePartnersCapacity);
			ps.println ("\t# capacity of the set of candidate partners");
		}
	}
}

// Local Variables: 
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
